package qc.module.qms.service.calc;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import qc.common.core.enums.qms.*;
import qc.common.core.utils.QmsTimeUtil;
import qc.module.qms.algorithm.NumericCalc.dto.*;
import qc.module.qms.algorithm.dto.QmsPointDto;
import qc.module.qms.entity.QmsPoint;
import qc.module.qms.entity.QmsStationCalcParam;
import qc.module.qms.entity.QmsStationRelation;
import qc.module.qms.mapper.QmsPointMapper;
import qc.module.qms.mapper.QmsStationCalcParamMapper;
import qc.module.qms.service.QmsBasicConfigCacheGetService;
import qc.module.qms.service.pointdata.QmsPointDataQueryService;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * QMS数值计算输入获取Service，获取数值计算所需输入的参数、输入的点号数据序列、输入的工作曲线
 * 2025-10-1，由于该Service需要调用QmsBasicConfigCacheGetService中的获取站点等信息，无法将该Service中的方法合并到QmsNumericCalcGetService中
 *
 * @author QuCheng Tech
 * @since 2025/9/8
 */
@Service
public class QmsNumericCalcInputGetService {
    private static final Logger logger = LoggerFactory.getLogger(QmsNumericCalcInputGetService.class);

    @Autowired
    private QmsBasicConfigCacheGetService basicConfigCacheGetService;

    @Autowired
    private QmsPointDataQueryService pointDataQueryService;

    /**
     * 获取指定计算方法和目标对象的输入，包含输入的参数、点号数据、工作曲线
     * 返回null表示参数错误，返回对象中的属性为null表示实际获取到的结果确实为空（如计算方法原本未设置参数、未设置输入点号数据）
     *
     * @param method 计算方法对象信息
     * @param dest   计算的目标对象信息
     * @return qc.module.qms.algorithm.NumericCalc.dto.QmsNumericCalcInput
     * @author QuCheng Tech
     * @since 2025/9/12
     */
    public QmsNumericCalcInput get(QmsNumericCalcUseMethod method, QmsNumericCalcDest dest) {
        //输入错误或不合理时视为不需要获取计算输入：计算方法有效、目标点号和时段类型有效
        if (method != null && StringUtils.isNotBlank(method.getMethodcode())
                && dest != null && dest.getPoint() != null && dest.getTmseg() != QmsPointTimeSegmentEnum.UN_DEFINE) {
            logger.debug("get 根据计算方法和计算目标对象获取所有输入，计算方法编码[" + method.getMethodcode() + "]，计算方法名称[" + method.getMethodname()
                    + "]，计算目标点号编码[" + dest.getPoint().getPointid() + "]，计算目标点号时段类型[" + dest.getTmseg() + "]，计算目标时间段["
                    + QmsTimeUtil.format(dest.getBeginTime(), dest.getTmseg()) + "]-[" + QmsTimeUtil.format(dest.getEndTime(), dest.getTmseg()) + "]");
            //计算目标点号关联的站点编码
            String destPointStcd = dest.getPoint().getStcd();
            //获取输入的顺序如下：1-先获取输入点号数据（后面获取参数可能需要输入数据点号中的所属站点）、2-再获取输入参数（3-根据输入参数类型判断获取工作曲线）
            QmsNumericCalcInput result = new QmsNumericCalcInput();
            //步骤1：获取输入点号数据
            List<QmsNumericCalcInputTimeSerialDatas> inputTimeSerialDatas = this.getPointDatas(method, dest);
            if (inputTimeSerialDatas != null && inputTimeSerialDatas.size() > 0x0) {
                logger.debug("get 根据计算方法和计算目标对象获取所有输入，获取到数据点号时序数据[" + inputTimeSerialDatas.size() + "]组");
                result.setTimeSerialDatas(inputTimeSerialDatas);
            }

            //输入的参数和工作曲线均通过计算方法的关联参数进行配置，因此判断计算方法的关联参数不为空
            if (method.getParams() != null && method.getParams().size() > 0x0) {
                List<QmsNumericCalcInputStationParams> inputStationParams = new ArrayList<>();
                //步骤2：获取输入参数
                //参数的所属站点有2种情况：一种是计算点号所属站点的参数、另一种是计算输入时序数据点号的所属站点的参数（如面雨量计算中各雨量站的权重参数、是否参与计算）
                //筛选出目标点号所属站点的参数和输入时序数据点号所属站点的参数
                //筛选时需要将参数值类型是工作曲线的过滤掉，工作曲线单独作为计算输入，不在输入参数中重复指定
                List<QmsNumericCalcUseMethodParam> destPointStationParams = method.getParams().stream().filter(p -> p.getIsinput() == false && p.getValuedatatype() != QmsParamValueDataTypeEnum.LINE).collect(Collectors.toList());
                List<QmsNumericCalcUseMethodParam> inputPointStationParams = method.getParams().stream().filter(p -> p.getIsinput() == true && p.getValuedatatype() != QmsParamValueDataTypeEnum.LINE).collect(Collectors.toList());
                //获取计算目标点号所属站点的参数
                if (destPointStationParams != null && destPointStationParams.size() > 0x0) {
                    logger.debug("get 根据计算方法和计算目标对象获取所有输入，计算方法中配置目标点号站点参数[" + destPointStationParams.size() + "]个");
                    QmsNumericCalcInputStationParams destPointStationParamValue = this.getStationParams(method.getMethodcode(), destPointStcd, destPointStationParams);
                    if (destPointStationParamValue != null)
                        inputStationParams.add(destPointStationParamValue);
                } else {
                    logger.debug("get 根据计算方法和计算目标对象获取所有输入，计算方法中未配置任何目标点号站点参数");
                }

                //获取计算输入时序数据点号所属站点的参数
                if (inputPointStationParams != null && inputPointStationParams.size() > 0x0) {
                    logger.debug("get 根据计算方法和计算目标对象获取所有输入，计算方法中配置输入时序数据点号站点参数[" + inputPointStationParams.size() + "]个");
                    //判断实际获取到的输入时序数据点号是否为空
                    if (inputTimeSerialDatas != null && inputTimeSerialDatas.size() > 0x0) {
                        for (QmsNumericCalcInputTimeSerialDatas serial : inputTimeSerialDatas) {
                            if (serial != null && StringUtils.isNotBlank(serial.getPointid()) && StringUtils.isNotBlank(serial.getStcd())) {
                                String inputPointDataStcd = serial.getStcd();
                                QmsNumericCalcInputStationParams inputPointDataStationParamValue = this.getStationParams(method.getMethodcode(), inputPointDataStcd, inputPointStationParams);
                                if (inputPointDataStationParamValue != null)
                                    inputStationParams.add(inputPointDataStationParamValue);
                            }
                        }
                    }
                } else {
                    logger.debug("get 根据计算方法和计算目标对象获取所有输入，计算方法中未配置任何输入时序数据点号站点参数");
                }

                //如果实际获取到的输入参数值不为空时才对返回结果中的输入参数进行赋值
                if (inputStationParams.size() > 0x0) {
                    logger.debug("get 根据计算方法和计算目标对象获取所有输入，获取到计算输入参数共[" + inputStationParams.size() + "]站点");
                    result.setParams(inputStationParams);
                } else {
                    logger.debug("get 根据计算方法和计算目标对象获取所有输入，未获取到任何站点的计算输入参数");
                }

                //步骤3：获取输入工作曲线
                List<QmsNumericCalcUseMethodParam> inputWorkLineParams = method.getParams().stream().filter(p -> p.getValuedatatype() == QmsParamValueDataTypeEnum.LINE).collect(Collectors.toList());
                if (inputWorkLineParams != null && inputWorkLineParams.size() > 0x0) {
                    logger.debug("get 根据计算方法和计算目标对象获取所有输入，获取到计算输入工作曲线[" + inputWorkLineParams.size() + "]个");
                    List<QmsNumericCalcInputWorkLine> inputWorkLines = new ArrayList<>();

                    for (QmsNumericCalcUseMethodParam param : inputWorkLineParams) {
                        //获取参数配置中的参数编码作为曲线编码
                        String workLineCode = param.getParamcode();
                        List<QmsNumericCalcInputWorkLine> lines = this.getWorkLines(workLineCode, destPointStcd);
                        if (lines != null && lines.size() > 0x0) {
                            inputWorkLines.addAll(lines);
                        }
                    }

                    //如果实际获取到的输入曲线不为空时才对返回结果中的输入曲线进行赋值
                    if (inputWorkLines.size() > 0x0) {
                        logger.debug("get 根据计算方法和计算目标对象获取所有输入，获取到计算输入工作曲线共[" + inputWorkLines.size() + "]条");
                        result.setWorkLines(inputWorkLines);
                    } else {
                        logger.debug("get 根据计算方法和计算目标对象获取所有输入，未获取到任何计算输入工作曲线");
                    }
                } else {
                    logger.debug("get 根据计算方法和计算目标对象获取所有输入，计算方法中未配置任何输入工作曲线");
                }
            }

            return result;
        }

        return null;
    }

    /**
     * 获取指定计算方法的输入点号时序数据序列(点号数据只要满足质量要求的)
     * 获取输入点号需要计算目标点号的监测要素、时段类型、起止时间作为输入条件
     *
     * @param method 计算方法信息
     * @param dest   计算目标对象信息
     * @return java.util.List<qc.module.qms.algorithm.NumericCalc.dto.QmsNumericCalcInputTimeSerialDatas>
     * @author QuCheng Tech
     * @since 2025/9/12
     */
    private List<QmsNumericCalcInputTimeSerialDatas> getPointDatas(QmsNumericCalcUseMethod method, QmsNumericCalcDest dest) {
        //输入错误或不合理时均视为不需要获取点号数据：计算方法有效并且指定的输入时序数据＞0
        if (method == null) {
            logger.error("getPointDatas 根据计算方法和计算目标对象获取输入点号数据序列，计算方法对象为空，输入错误无法获取输入点号数据序列");
            return null;
        }
        if (StringUtils.isBlank(method.getMethodcode())) {
            logger.error("getPointDatas 根据计算方法和计算目标对象获取输入点号数据序列，计算方法编码为空，输入错误无法获取输入点号数据序列");
            return null;
        }
        //配置的输入时序数据序列数量为0时不获取即可，不记录为错误
        if (method.getIntscount() <= 0x0) {
            logger.info("getPointDatas 根据计算方法和计算目标对象获取输入点号数据序列，计算方法中设置的输入时序数据序列数量≤0，无需获取输入点号数据序列");
            return null;
        }
        if (method.getIntsdatas() == null || method.getIntsdatas().size() <= 0x0) {
            logger.error("getPointDatas 根据计算方法和计算目标对象获取输入点号数据序列，计算方法关联的输入点号数据配置为空或数量为0，输入错误无法获取输入点号数据序列");
            return null;
        }
        //计算目标对象、目标点号、目标点号中的站点编码和时段类型有效，计算目标中的起始和截止时间有效
        if (dest == null) {
            logger.error("getPointDatas 根据计算方法和计算目标对象获取输入点号数据序列，计算目标对象为空，输入错误无法获取输入点号数据序列");
            return null;
        }
        if (dest.getPoint() == null) {
            logger.error("getPointDatas 根据计算方法和计算目标对象获取输入点号数据序列，计算目标中的点号对象为空，输入错误无法获取输入点号数据序列");
            return null;
        }
        if (StringUtils.isBlank(dest.getPoint().getStcd())) {
            logger.error("getPointDatas 根据计算方法和计算目标对象获取输入点号数据序列，计算目标中点号的站点编码为空，输入错误无法获取输入点号数据序列");
            return null;
        }
        if (dest.getTmseg() == QmsPointTimeSegmentEnum.UN_DEFINE) {
            logger.error("getPointDatas 根据计算方法和计算目标对象获取输入点号数据序列，计算目标中的点号时段类型无效，输入错误无法获取输入点号数据序列");
            return null;
        }
        if (dest.getBeginTime() == null) {
            logger.error("getPointDatas 根据计算方法和计算目标对象获取输入点号数据序列，计算目标中的起始时间为空，输入错误无法获取输入点号数据序列");
            return null;
        }
        if (dest.getEndTime() == null) {
            logger.error("getPointDatas 根据计算方法和计算目标对象获取输入点号数据序列，计算目标中的截止时间为空，输入错误无法获取输入点号数据序列");
            return null;
        }
        //起始时间和截止时间有效性判断
        if (dest.getBeginTime().isAfter(dest.getEndTime())) {
            logger.error("getPointDatas 根据计算方法和计算目标对象获取输入点号数据序列，计算目标中的起始时间＞截止时间，输入错误无法获取输入点号数据序列");
            return null;
        }

        String destPointid = dest.getPoint().getPointid();
        String destPointStcd = dest.getPoint().getStcd();
        String destPointElementCode = dest.getPoint().getElecode();
        QmsPointTimeSegmentEnum destTmseg = dest.getTmseg();
        //计算目标的起止时间，在此再根据目标时段类型计算一次；
        //起始时间：输入的起始时间所在的时段初时间; 截止时间：输入的截止时间所在的时段初时间
        LocalDateTime destBeginPeriodTime = QmsTimeUtil.getPeriodTime(dest.getBeginTime(), destTmseg);
        LocalDateTime destEndPeriodTime = QmsTimeUtil.getPeriodTime(dest.getEndTime(), destTmseg);

        logger.debug("getPointDatas 根据计算方法和计算目标对象获取输入点号数据序列，计算方法编码[" + method.getMethodcode() + "]，计算方法名称[" + method.getMethodname()
                + "]，计算目标点号编码[" + destPointid + "]，计算目标点号时段类型[" + destTmseg + "]，计算目标时间段["
                + QmsTimeUtil.format(destBeginPeriodTime, destTmseg) + "]-[" + QmsTimeUtil.format(destEndPeriodTime, destTmseg) + "]，共有[" + method.getIntsdatas().size() + "]个输入点号数据序列");

        List<QmsNumericCalcInputTimeSerialDatas> result = new ArrayList<>();

        //输入数据的顺序按照计算方法关联的输入数据中指定的顺序，在此进行排序后再遍历获取点号数据
        List<QmsNumericCalcUseMethodInputTsDatas> sortedInputTsDatas = method.getIntsdatas().stream().sorted(Comparator.comparingInt(QmsNumericCalcUseMethodInputTsDatas::getOdr)).collect(Collectors.toList());
        //记录输入时序数据的序号，从1开始
        int inputTsDatasConfigIndex = 0x0;
        for (QmsNumericCalcUseMethodInputTsDatas inputTsDatasConfig : sortedInputTsDatas) {
            inputTsDatasConfigIndex++;
            //调试输出输入数据配置信息
            logger.debug("getPointDatas 根据计算方法和计算目标对象获取第[" + inputTsDatasConfigIndex + "]个输入点号数据序列，输入数据配置项ID[" + inputTsDatasConfig.getId()
                    + "]，站点关系[" + inputTsDatasConfig.getStationrelate().toString() + "]，站点类型编码[" + inputTsDatasConfig.getStationctgcode() + "]，站点扩展标记[" + inputTsDatasConfig.getStationext()
                    + "]，是否同要素[" + (inputTsDatasConfig.getElesame() == true) + "]，要素编码[" + inputTsDatasConfig.getElecode() + "]，要素扩展标记[" + inputTsDatasConfig.getEleext()
                    + "]，取点号时段类型[" + inputTsDatasConfig.getTmsegtype().toString() + "]，指定时段类型[" + inputTsDatasConfig.getTmseg().toString()
                    + "]，取值字段[" + inputTsDatasConfig.getValuefield().toString() + "]，输入顺序号[" + inputTsDatasConfig.getOdr()
                    + "]，查询时间段类型[" + inputTsDatasConfig.getTmrange().toString() + "]，起始偏移[" + inputTsDatasConfig.getTmbeginoffset() + "]，截止偏移[" + inputTsDatasConfig.getTmendoffset()
                    + "]，不包含起始[" + (inputTsDatasConfig.getExcludebegintm() == true) + "]，不包含截止[" + (inputTsDatasConfig.getExcludeendtm() == true)
                    + "]，需要起始时间往前最近数据[" + (inputTsDatasConfig.getNeedbegintmlatest() == true) + "]，往前偏移[" + inputTsDatasConfig.getBegintmlatestoffset() + "]");
            //一个关联的输入点号数据配置可能对应一个或多个点号的数据：先获取站点（集合）、再遍历站点获取点号、根据点号查询时序数据
            List<String> stcds = new ArrayList<>();
            //根据计算法方法关联的输入数据中的站点关系进行获取
            QmsQueryPointDataStationRelationTypeEnum stationRelationType = inputTsDatasConfig.getStationrelate();
            if (stationRelationType == QmsQueryPointDataStationRelationTypeEnum.SAME_TO_DEST) {
                logger.debug("getPointDatas 根据计算方法和计算目标对象获取第[" + inputTsDatasConfigIndex + "]个输入点号数据序列，输入数据站点编码与目标相同[" + destPointStcd + "]");
                //获取数据的站点与计算目标中的站点相同
                stcds.add(destPointStcd);
            } else {
                //如果是要获取关联的站点，关联的站点分类必须指定
                if (StringUtils.isBlank(inputTsDatasConfig.getStationctgcode())) {
                    logger.error("getPointDatas 根据计算方法和计算目标对象获取第[" + inputTsDatasConfigIndex + "]个输入点号数据序列，输入数据站点关系[" + stationRelationType.getName() + "]，关联站点分类编码为空无法获取站点信息");
                } else {
                    //判断关联关系的方向，from还是to
                    boolean isRelatedFrom = false;
                    if (stationRelationType == QmsQueryPointDataStationRelationTypeEnum.RELATE_FROM_DEST)
                        isRelatedFrom = true;
                    List<QmsStationRelation> stationRelations = basicConfigCacheGetService.getStationRelations(destPointStcd, isRelatedFrom, inputTsDatasConfig.getStationctgcode(), inputTsDatasConfig.getStationext());
                    if (stationRelations == null || stationRelations.size() <= 0x0) {
                        //获取到关联关系的站点集合为空，可能是关联关系设置错误或输入数据中的配置错误
                        logger.warn("getPointDatas 根据计算方法和计算目标对象获取第[" + inputTsDatasConfigIndex + "]个输入点号数据序列，输入数据站点关系[" + stationRelationType.getName()
                                + "]，站点分类编码[" + inputTsDatasConfig.getStationctgcode() + "]，站点扩展标记[" + inputTsDatasConfig.getStationext() + "]，获取到的关联站点(集合)为空");
                    } else {
                        logger.debug("getPointDatas 根据计算方法和计算目标对象获取第[" + inputTsDatasConfigIndex + "]个输入点号数据序列，输入数据站点关系[" + stationRelationType.getName()
                                + "]，站点分类编码[" + inputTsDatasConfig.getStationctgcode() + "]，站点扩展标记[" + inputTsDatasConfig.getStationext() + "]，获取到的关联站点共[" + stationRelations.size() + "]个");
                        //站点关联关系按排序号排序后加入到站点编码集合中，在获取站点关联关系中返回结果已经排序不需要重复排序
                        if (isRelatedFrom == false)
                            stcds = stationRelations.stream().map(p -> p.getRelatestcd()).collect(Collectors.toList());
                        else
                            stcds = stationRelations.stream().map(p -> p.getStcd()).collect(Collectors.toList());
                    }
                }
            }

            //判断获取到的站点集合，遍历逐个站点获取点号
            if (stcds.size() <= 0x0) {
                logger.debug("getPointDatas 根据计算方法和计算目标对象获取第[" + inputTsDatasConfigIndex + "]个输入点号数据序列，输入数据站点关系[" + stationRelationType.getName()
                        + "]，站点分类编码[" + inputTsDatasConfig.getStationctgcode() + "]，站点扩展标记[" + inputTsDatasConfig.getStationext() + "]，未获取到任何关联站点");
            } else {
                //获取监测要素编码，一个输入数据序列中只能设置唯一一个监测要素编码
                String elementCode = destPointElementCode;
                //如果输入点号数据系列配置中未配置或无效，默认均使用同目标相同要素编码
                if (inputTsDatasConfig.getElesame() == false) {
                    //输入点号的监测要素与目标不同
                    if (StringUtils.isBlank(inputTsDatasConfig.getElecode())) {
                        logger.warn("getPointDatas 根据计算方法和计算目标对象获取第[" + inputTsDatasConfigIndex + "]个输入点号数据序列，输入数据站点关系[" + stationRelationType.getName()
                                + "]，站点分类编码[" + inputTsDatasConfig.getStationctgcode() + "]，站点扩展标记[" + inputTsDatasConfig.getStationext() + "]，获取到的关联站点(集合)为空");
                    } else {
                        //有设置输入点号的监测要素编码
                        elementCode = inputTsDatasConfig.getElecode();
                    }
                }

                //输入点号数据项中的监测要素编码
                String elementExt = inputTsDatasConfig.getEleext();

                //构造查询点号数据的时间段需要用于点号的时段类型，对一个输入数据项来说输入的点号时段类型相同，在此根据第一个站点的点号获取一次即可
                //查询点号的时段类型需要根据每个点号中的配置来，默认保持与计算目标时段类型一致
                QmsPointTimeSegmentEnum queryPointDataTimeSegment = destTmseg;
                //如果输入点号数据项中配置为指定的时段类型并且时段类型有效，使用配置的指定时段类型
                if (inputTsDatasConfig.getTmsegtype() == QmsQueryPointDataTimeSegmentTypeEnum.DESIGNATED
                        && inputTsDatasConfig.getTmseg() != QmsPointTimeSegmentEnum.UN_DEFINE) {
                    //指定的时段类型有效
                    queryPointDataTimeSegment = inputTsDatasConfig.getTmseg();
                    logger.debug("getPointDatas 根据计算方法和计算目标对象获取第[" + inputTsDatasConfigIndex + "]个输入点号数据序列，取点号时段类型[" + inputTsDatasConfig.getTmsegtype().toString()
                            + "]，指定时段类型[" + inputTsDatasConfig.getTmseg().toString() + "]，设置查询输入点号数据时段类型为[" + queryPointDataTimeSegment.toString() + "]");
                } else if (inputTsDatasConfig.getTmsegtype() == QmsQueryPointDataTimeSegmentTypeEnum.SMALLER_ONE_THAN_DEST) {
                    //如果输入点号数据项中配置为比目标点号小一个时段类型，获取输入点号的有效时段类型进行计算；
                    //不需要获取输入点号中的时段类型，直接使用目前时段类型在目标点号中获取小一个时段的时段类型
                    queryPointDataTimeSegment = QmsPointTimeSegmentEnum.getSmallerOneTimeSegmentEnum(dest.getPoint().getTmsegs(), destTmseg);
                    logger.debug("getPointDatas 根据计算方法和计算目标对象获取第[" + inputTsDatasConfigIndex + "]个输入点号数据序列，取点号时段类型[" + inputTsDatasConfig.getTmsegtype().toString()
                            + "]，取计算目标时段类型小一个时段类型，设置查询输入点号数据时段类型为[" + queryPointDataTimeSegment.toString() + "]");
                    //QmsPointDto firstPointDto = basicConfigCacheGetService.getPointDtoByStcdAndElementCode(stcds.get(0x0), elementCode, elementExt);
                    //if (firstPointDto == null) {
                    //    logger.warn("getPointDatas 根据计算方法和计算目标对象获取第["+inputTsDatasConfigIndex+"]个输入点号数据序列，计算方法编码[" + method.getMethodcode() + "]，计算目标点号编码[" + destPointid
                    //            + "]，计算目标点号时段类型[" + destTmseg.toString() + "]，输入数据配置项ID[" + inputTsDatasConfig.getId() + "]，根据站点编码[" + stcds.get(0x0)
                    //            + "]，监测要素编码[" + elementCode + "]，监测要素扩展标记[" + elementExt + "]，未能获取到对应的点号");
                    //} else {
                    //    //需要获取输入点号中配置的所有时段类型，获取小一个时段类型
                    //    queryPointDataTimeSegment = QmsPointTimeSegmentEnum.getSmallerOneTimeSegmentEnum(firstPointDto.getTmsegs(), destTmseg);
                    //
                    //    logger.debug("getPointDatas 根据计算方法和计算目标对象获取第["+inputTsDatasConfigIndex+"]个输入点号数据序列，计算方法编码[" + method.getMethodcode() + "]，计算目标点号编码[" + destPointid
                    //            + "]，计算目标点号时段类型[" + destTmseg.toString() + "]，输入数据配置项ID[" + inputTsDatasConfig.getId() + "]，根据站点编码[" + stcds.get(0x0)
                    //            + "]，监测要素编码[" + elementCode + "]，监测要素扩展标记[" + elementExt + "]，获取到输入数据点号[" + firstPointDto.getPointid() + "]，计算得到输入点号数据时段类型[" + queryPointDataTimeSegment.getName() + "]");
                    //}
                }

                //判断获取到的查询点号时段类型
                if (queryPointDataTimeSegment == QmsPointTimeSegmentEnum.UN_DEFINE) {
                    //无效，无效时使用同目标时段类型继续获取输入点号数据
                    queryPointDataTimeSegment = destTmseg;

                    logger.warn("getPointDatas 根据计算方法和计算目标对象获取第[" + inputTsDatasConfigIndex + "]个输入点号数据序列，输入数据配置项ID[" + inputTsDatasConfig.getId()
                            + "]，取点号时段类型[" + inputTsDatasConfig.getTmsegtype().getName() + "]，指定时段类型[" + inputTsDatasConfig.getTmseg().toString()
                            + "]，未能获取到有效的查询数据时段类型，查询时序数据使用时段类型[" + queryPointDataTimeSegment.getName() + "]");
                } else {
                    logger.debug("getPointDatas 根据计算方法和计算目标对象获取第[" + inputTsDatasConfigIndex + "]个输入点号数据序列，输入数据配置项ID[" + inputTsDatasConfig.getId()
                            + "]，取点号时段类型[" + inputTsDatasConfig.getTmsegtype().getName() + "]，指定时段类型[" + inputTsDatasConfig.getTmseg().toString()
                            + "]，查询时序数据使用时段类型[" + queryPointDataTimeSegment.getName() + "]");
                }

                //查询数据的时间对所有站点均相同，在此进行处理；由于计算目标时间可能是一段时间（不只是一个时间点），因此不管是取定点数据还是最近一条数据均需要按时间段来查询点号数据
                //计算目标的起始和截止时间点必须有
                //根据计算目标起始时间计算出获取数据的起始时间点：默认查询数据的起始时间就是时段开始的时间点
                //根据计算目标截止时间计算出获取数据的截止时间点：默认查询数据的截止时间是时段结束的时间点（下一个时段的开始时间点）
                //为避免LocalDateTime为引用类型，使用时段起始时间重新创建一个对象，不使用直接变量赋值方式
                //LocalDateTime queryPointDataBeginTime = destBeginPeriodTime;
                LocalDateTime queryPointDataBeginTime = LocalDateTime.of(destBeginPeriodTime.toLocalDate(), destBeginPeriodTime.toLocalTime());
                LocalDateTime queryPointDataEndTime = QmsTimeUtil.getNextPeriodTime(destEndPeriodTime, destTmseg);
                //查询不包含起始时间点，默认为false表示包含起始时间点
                boolean excludeBeginTime = false;
                //查询不包含截止时间点，默认为false表示包含截止时间点；2025-9-27修改默认值为true表示默认不包含截止时间点
                //比如：计算目标为日数据，2025-9-27；提取输入数据如为小时数据，提取输入数据的时间段为：2025-9-27 00:00:00~2025-9-28 00:00:00，此时截止时间点的数据应不包含在内
                boolean excludeEndTime = true;
                QmsPointValueFieldEnum valueField = inputTsDatasConfig.getValuefield();//查询数据的字段，确定是要值、时段初值、时段末值还是最值
                boolean needBeginTimeLatestData = false;//是否需要查询起始时间点前的最新一条数据（如果起始时间点数据不存在时需要最近一条），默认为false表示不需要
                int beginTimeLatestDataInterval = 0x0;//如果要查询起始时间点前的最新一条数据时往前取数据的最大时段数，默认为0时表示不查询(只要对应时间点的数据)，为负值时表示不限制时间，大于0的值时表示限制时段数

                //如果查询点号数据的起止时段有偏移进行处理
                if (inputTsDatasConfig.getTmbeginoffset() != null && inputTsDatasConfig.getTmbeginoffset().intValue() != 0x0) {
                    //如果查询点号数据的起始时间偏移时段数有效（不为空、不为0），可以为正负（自动取绝对值，只能往前指定正数时段数）
                    int beforeInterval = 0x0 - Math.abs(inputTsDatasConfig.getTmbeginoffset().intValue());
                    queryPointDataBeginTime = QmsTimeUtil.getIntervalPeriodTime(destBeginPeriodTime, queryPointDataTimeSegment, beforeInterval);
                }
                if (inputTsDatasConfig.getTmendoffset() != null && inputTsDatasConfig.getTmendoffset().intValue() != 0x0) {
                    //如果查询点号数据的截止时间偏移时段数有效（不为空、不为0），可以为正负（自动取绝对值，只能往后指定正数时段数）
                    int afterInterval = Math.abs(inputTsDatasConfig.getTmendoffset().intValue());
                    queryPointDataEndTime = QmsTimeUtil.getIntervalPeriodTime(queryPointDataEndTime, queryPointDataTimeSegment, afterInterval);
                }
                //是否包含起始时间点、是否包含截止时间点、是否需要起始时间点前的最新一条数据
                if (inputTsDatasConfig.getExcludebegintm() != null && inputTsDatasConfig.getExcludebegintm().booleanValue() == true)
                    excludeBeginTime = true;
                if (inputTsDatasConfig.getExcludeendtm() != null && inputTsDatasConfig.getExcludeendtm().booleanValue() == false)
                    excludeEndTime = false;
                if (inputTsDatasConfig.getNeedbegintmlatest() != null && inputTsDatasConfig.getNeedbegintmlatest().booleanValue() == true)
                    needBeginTimeLatestData = true;
                //如果要查询起始时间点前的最新一条数据时往前取数据的最大时段数
                if (inputTsDatasConfig.getBegintmlatestoffset() != null)
                    beginTimeLatestDataInterval = inputTsDatasConfig.getBegintmlatestoffset().intValue();

                //遍历站点编码集合，逐个获取对应点号进行时序数据查询
                for (String stcd : stcds) {
                    QmsPointDto pointDto = null;
                    //如果获取的点号数据是比目标点号小一个时段类型：判断当前站点编码如果就是目前站点编码则直接使用目标点号；如果当前站点编码与目标站点编码不同才需要从站点与点号关联关系中获取
                    if (inputTsDatasConfig.getTmsegtype() == QmsQueryPointDataTimeSegmentTypeEnum.SMALLER_ONE_THAN_DEST && StringUtils.equalsIgnoreCase(destPointStcd, stcd)) {
                        pointDto = new QmsPointDto();
                        BeanUtils.copyProperties(dest.getPoint(), pointDto); //复制对象属性
                    } else {
                        QmsPoint point = basicConfigCacheGetService.getPointByStcdAndElementCode(stcd, elementCode, elementExt);
                        if (point != null)
                            pointDto = QmsPointMapper.MAPPER.toDto(point);
                    }

                    if (pointDto == null) {
                        logger.warn("getPointDatas 根据计算方法和计算目标对象获取第[" + inputTsDatasConfigIndex + "]个输入点号数据序列，输入数据配置项ID[" + inputTsDatasConfig.getId() + "]，根据站点编码[" + stcd
                                + "]，监测要素编码[" + elementCode + "]，监测要素扩展标记[" + elementExt + "]，未能获取到对应的点号，无法进行点号时序数据查询");
                    } else {
                        logger.debug("getPointDatas 根据计算方法和计算目标对象获取第[" + inputTsDatasConfigIndex + "]个输入点号数据序列，输入数据配置项ID[" + inputTsDatasConfig.getId() + "]，根据站点编码[" + stcd
                                + "]，监测要素编码[" + elementCode + "]，监测要素扩展标记[" + elementExt + "]，获取到输入数据点号[" + pointDto.getPointid() + "]");

                        //初始化一个有序的数据集合，确保按查询出的点号时序数据的时标Asc排列
                        Map<LocalDateTime, Double> pointDatas = new LinkedHashMap<>();

                        //如果计算目标的起始时间和截止时间不是同一个时间点，表示本次需要计算多个时间点的数据，取输入点号数据均是要取一段时间数据
                        Map<LocalDateTime, Double> pointRangeDatas = pointDataQueryService.getRangeValidValues(pointDto.getPointid(), queryPointDataTimeSegment, queryPointDataBeginTime, queryPointDataEndTime, valueField, excludeBeginTime, excludeEndTime);
                        //如果配置项中配置需要起始时间往前最新数据，判断起始时间点是否有查询结果数据，判断是否要往前查询最新一条数据
                        if (needBeginTimeLatestData == true) {
                            //如果查询起始时间最新数据往前时段数设置为0--表示只查询起始时间点的数据，在查询时间段数据中已经包含起始时间点，此时不需要再重复查询
                            if (beginTimeLatestDataInterval != 0x0 || excludeBeginTime == true) {
                                //beginTimeLatestDataInterval为0时表示不查询(只要对应时间点的数据)，为负值时表示不限制时间，大于0的值时表示限制时段数
                                //判断查询的时间段数据结果中是否包含起始时间点数据，如果不包含根据起始时间点往前查询最近一条数据
                                boolean rangeDataContainsBeginTime = false;
                                if (pointRangeDatas != null && pointRangeDatas.size() > 0x0) {
                                    //判断起始时间点是否已有数据
                                    if (pointRangeDatas.containsKey(queryPointDataBeginTime))
                                        rangeDataContainsBeginTime = true;
                                }
                                //判断结果为需要查询起始时间点最近一条数据
                                if (rangeDataContainsBeginTime == false) {
                                    LocalDateTime limitQueryBeginTimeLatestData = null;//先初始化限制往前的时间为null，表示不限制
                                    //计算查询最新数据的限制时间
                                    if (beginTimeLatestDataInterval > 0x0)//如果设置往前取数据的时段数＞0，根据点号的时段类型往前
                                        limitQueryBeginTimeLatestData = QmsTimeUtil.getIntervalPeriodTime(queryPointDataBeginTime, queryPointDataTimeSegment, -beginTimeLatestDataInterval);

                                    //查询指定时间往前最新一条数据
                                    Map<LocalDateTime, Double> pointLatestData = pointDataQueryService.getPointValidValue(pointDto.getPointid(), queryPointDataTimeSegment, queryPointDataBeginTime, valueField, limitQueryBeginTimeLatestData);
                                    if (pointLatestData != null && pointLatestData.size() > 0x0) {
                                        //如果起始时间点最近数据查询结果不为空，先添加到集合中（保证按数据时标的先后顺序）
                                        for (Map.Entry<LocalDateTime, Double> entry : pointLatestData.entrySet()) {
                                            pointDatas.put(entry.getKey(), entry.getValue());
                                        }
                                    }
                                }
                            }
                        }

                        //设置添加到结果中的输入数据序列
                        QmsNumericCalcInputTimeSerialDatas serialDatas = new QmsNumericCalcInputTimeSerialDatas();
                        serialDatas.setSerialName(pointDto.getPointfullname());
                        serialDatas.setPointid(pointDto.getPointid());
                        serialDatas.setStcd(stcd);
                        serialDatas.setTmseg(queryPointDataTimeSegment);
                        //如果查询的点号时序数据结果有数据时才添加
                        if (pointRangeDatas != null && pointRangeDatas.size() > 0x0) {
                            for (Map.Entry<LocalDateTime, Double> entry : pointRangeDatas.entrySet()) {
                                pointDatas.put(entry.getKey(), entry.getValue());
                            }
                        }
                        if (pointDatas.size() > 0x0)
                            serialDatas.setDatas(pointDatas);

                        result.add(serialDatas);
                    }
                }
            }
        }

        return result;
    }

    /**
     * 获取指定站点的指定参数集合
     * 每个参数项不管实际是否有配置参数均在返回集合中列出，用以区分是计算方法没有配置参数项还是参数项没有配置参数值
     *
     * @param methodCode 计算方法编码
     * @param stcd       站点编码
     * @param params     参数项集合
     * @return qc.module.qms.algorithm.NumericCalc.dto.QmsNumericCalcInputStationParams
     * @author QuCheng Tech
     * @since 2025/9/12
     */
    private QmsNumericCalcInputStationParams getStationParams(String methodCode, String stcd, List<QmsNumericCalcUseMethodParam> params) {
        //输入错误或不合理时视为不需要获取参数：站点编码不能为空、参数集合不能为空
        if (StringUtils.isNotBlank(stcd) && params != null && params.size() > 0x0) {
            logger.debug("getStationParams 获取站点计算参数，站点编码[" + stcd + "]，参数数量[" + params.size() + "]");
            //输入的参数项集合再进行一次筛选，去除工作曲线参数；不需要重复处理，本方法调用传入时已做过滤处理
            QmsNumericCalcInputStationParams result = new QmsNumericCalcInputStationParams();
            result.setStcd(stcd);

            List<QmsNumericCalcInputParamItem> items = new ArrayList<>();

            //获取参数编码集合
            List<String> paramCodes = params.stream().map(p -> p.getParamcode()).collect(Collectors.toList());
            List<QmsStationCalcParam> stationAllParamValues = basicConfigCacheGetService.getStationCalcParams(methodCode, stcd, paramCodes);
            //不管获取站点的参数值是否为空，计算方法的每个参数项均需要在返回结果中
            //逐个遍历计算方法的参数项
            for (QmsNumericCalcUseMethodParam param : params) {
                //参数项编码
                String paramCode = param.getParamcode();
                QmsNumericCalcInputParamItem item = new QmsNumericCalcInputParamItem();
                item.setParam(param);

                //如果获取到的参数值不为空，获取对应参数项配置的参数值集合
                if (stationAllParamValues != null && stationAllParamValues.size() > 0x0) {
                    //根据参数项编码筛选
                    List<QmsStationCalcParam> filterByParamCodeValues = stationAllParamValues.stream().filter(p -> StringUtils.equalsIgnoreCase(p.getParamcode(), paramCode)).collect(Collectors.toList());
                    if (filterByParamCodeValues != null && filterByParamCodeValues.size() > 0x0) {
                        List<QmsNumericCalcInputParamValue> values = new ArrayList<>();

                        for (QmsStationCalcParam paramEntity : filterByParamCodeValues) {
                            values.add(QmsStationCalcParamMapper.MAPPER.entityToNumericCalcInputParamValue(paramEntity));
                        }

                        item.setValues(values);
                    }
                }

                items.add(item);
            }

            if (items.size() > 0x0)
                result.setItems(items);

            return result;
        }

        return null;
    }

    /**
     * 获取指定站点的指定编码的工作曲线（二维、三维曲线）
     *
     * @param stcd     站点编码
     * @param lineCode 曲线编码，计算方法配置参数项中的参数编码
     * @return java.util.List<qc.module.qms.algorithm.NumericCalc.dto.QmsNumericCalcInputWorkLine>
     * @author QuCheng Tech
     * @since 2025/9/12
     */
    private List<QmsNumericCalcInputWorkLine> getWorkLines(String stcd, String lineCode) {
        //自动获取输入参数配置中的工作曲线，可能一个曲线存在多个配置的值
        //如果曲线没有时返回数据中仅包含曲线编码，不包含曲线数值序列；用于区分是工作曲线参数没有配置还是工作曲线没有配置
        if (StringUtils.isNotBlank(stcd) && StringUtils.isNotBlank(lineCode)) {
            logger.debug("getWorkLines 获取站点工作曲线，站点编码[" + stcd + "]，曲线编码[" + lineCode + "]");
            //// TODO: 2025/9/12 调用查询工作曲线Service中的方法 
        }

        return null;
    }

}
